package org.ndx.jsg.output.freemarker;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.configuration.Configuration;
import org.ndx.jsg.FileInfos;
import org.ndx.jsg.MimeTypes;
import org.ndx.jsg.config.DefaultConfiguration;
import org.ndx.jsg.input.DefaultFileInfosProvider;
import org.ndx.jsg.input.xml.JSGXmlBuilder;
import org.ndx.jsg.output.FileCopier;
import org.ndx.jsg.output.OutputProvider;
import org.ndx.jsg.util.JsgException;
import org.xml.sax.SAXException;

import com.google.inject.Inject;

import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class JsgXmlTransformer implements OutputProvider, TemplateProvider {
	public static final String JSG_TEMPLATES_EXTENSION = "jsg.templates.extension";
	public static final String JSG_LIBRARIES_EXTENSION = "jsg.libraries.extension";
	private static Logger logger = Logger.getLogger(JsgXmlTransformer.class
			.getName());
	/**
	 * Templates used to process all files in one shot
	 */
	private Collection<Template> globalTemplates = new LinkedList<Template>();

	/**
	 * Templates used to process all files of each directories at the same time
	 */
	private Collection<Template> directoriesTemplates = new LinkedList<Template>();

	/**
	 * Templates used to process one file and dump the output
	 */
	private Collection<Template> fileTemplates = new LinkedList<Template>();

	/**
	 * Output root, memorized for easier processing
	 */
	private String outputRoot;

	/**
	 * Data structure arranging processable files the best possible way.
	 */
	@Inject
	private ProcessableFiles processableFiles;

	/**
	 * Prepare output by loading all templates.
	 * Templates are files ending with correct suffix. file templates contains the __filepath__ element, while directory ones contains the __directory__ one, and global none of the above
	 */
	@Override
	public void prepare(final Configuration config) {
		freemarker.template.Configuration freemarkerConfig = new freemarker.template.Configuration();
		outputRoot = config.getString(DefaultConfiguration.OUTPUT_PATH);
		// Load configuration directory
		String templatesPath = getTemplatesPath(config);
		File templates = new File(templatesPath);
		if (!templates.exists()) {
			File configPath = new File(config
					.getString(DefaultConfiguration.CONFIGURATION_FILE));
			templates = new File(configPath.getParent() + File.separator
					+ templatesPath);
		}
		copyNonTemplates(templates, config);
		try {
			freemarkerConfig.setDirectoryForTemplateLoading(templates);
			freemarkerConfig.setObjectWrapper(new DefaultObjectWrapper());
			File[] usable = templates.listFiles(new FilenameFilter() {
				
				@Override
				public boolean accept(File dir, String name) {
					return name.endsWith(getTemplatesExtension(config));
				}
			});
			for(File f : usable) {
				if(f.getName().contains(NameResolver.TEMPLATE_FILEPATH) || (f.getName().contains(NameResolver.TEMPLATE_FILENAME) && f.getName().contains(NameResolver.TEMPLATE_DIRPATH))) {
					fileTemplates.add(freemarkerConfig.getTemplate(f.getName()));
				} else if(f.getName().contains(NameResolver.TEMPLATE_DIRECTORY)) {
					directoriesTemplates.add(freemarkerConfig.getTemplate(f.getName()));
				} else {
					globalTemplates.add(freemarkerConfig.getTemplate(f.getName()));
				}
			}
		} catch (Exception e) {
			throw new JsgException(
					"unable to prepare freemarker using template directory "
							+ templates.getAbsolutePath(), e);
		}
	}

	public static String getTemplatesExtension(Configuration config) {
		return config
				.getString(JSG_TEMPLATES_EXTENSION, ".template");
	}

	public static String getLibrariesExtension(Configuration config) {
		return config
				.getString(JSG_LIBRARIES_EXTENSION, ".ftl");
	}

	public static String getTemplatesPath(Configuration config) {
		return config.getString(DefaultConfiguration.JSG_TEMPLATES_DIRECTORY,
				"templates");
	}

	/**
	 * Transform all files using given set of templates. Once it is done, copy
	 * all required files from the template directory to the output directory.
	 */
	@Override
	public void terminate(Configuration config) {
		processableFiles.process(this);
	}

	private void copyNonTemplates(File templates, Configuration config) {
		if(logger.isLoggable(Level.FINE)) {
			logger.fine("copying templatess associated files through direct copy");
		}
		DefaultFileInfosProvider infos = new DefaultFileInfosProvider(
				new NonTemplateBuidler(config));
		Collection<FileInfos> toCopy = infos
				.getFiles(
						templates,
						new String[] {""},
						Arrays
								.asList(config
										.getStringArray(DefaultConfiguration.INPUT_IGNORED_NAMES)));
		FileCopier fileCopier = new FileCopier();
		for(FileInfos f : toCopy) {
			fileCopier.handle(f);
		}
		fileCopier.terminate(config);
	}

	/**
	 * When handling file, we check if its format is correct. If so, we add it
	 * to processableFiles and then TODO extract some metadata from it (like
	 * title, tags, date, ...)
	 */
	@Override
	public void handle(FileInfos infos) {
		if (infos.getFile().getName().endsWith(JSGXmlBuilder.JSG_XML_EXTENSION)) {
			if (logger.isLoggable(Level.FINE)) {
				logger.fine("file " + infos.getRelativePath()
						+ " is marked as processable");
			}
			processableFiles.add(infos);
		}
	}

	public Collection<Template> getGlobalTemplates() {
		return globalTemplates;
	}

	public Collection<Template> getDirectoriesTemplates() {
		return directoriesTemplates;
	}

	public Collection<Template> getFileTemplates() {
		return fileTemplates;
	}

	public String getOutputRoot() {
		return outputRoot;
	}
}
